﻿using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace GMC_4
{
    class Vm
    {
        [DllImport("user32.dll")]
        private static extern short GetKeyState(int keyCode);
        [DllImport("kernel32.dll")]
        public static extern bool Beep(int freq, int duration);

        static readonly byte RegA = 0x6f;
        static readonly byte RegB = 0x6c;
        static readonly byte RegY = 0x6e;
        static readonly byte RegZ = 0x6d;

        static readonly byte RegAa = 0x69;
        static readonly byte RegBb = 0x67;
        static readonly byte RegYy = 0x68;
        static readonly byte RegZz = 0x66;

        public static int MemorySize { get { return 0x70; } }

        public byte A
        {
            get { return M[RegA]; }
            set { M[RegA] = value; }
        }
        public byte B
        {
            get { return M[RegB]; }
            set { M[RegB] = value; }
        }
        public byte Y
        {
            get { return M[RegY]; }
            set { M[RegY] = value; }
        }
        public byte Z
        {
            get { return M[RegZ]; }
            set { M[RegZ] = value; }
        }

        public byte Aa
        {
            get { return M[RegAa]; }
            set { M[RegAa] = value; }
        }
        public byte Bb
        {
            get { return M[RegBb]; }
            set { M[RegBb] = value; }
        }
        public byte Yy
        {
            get { return M[RegYy]; }
            set { M[RegYy] = value; }
        }
        public byte Zz
        {
            get { return M[RegZz]; }
            set { M[RegZz] = value; }
        }

        public byte Pc { get; private set; }
        public bool Status { get; private set; }

        public byte[] M;

        public byte LED { get; private set; }
        public byte SevenSegmentDisplay { get; private set; }

        public event Action<Vm> SevenSegmentDisplayChanged;
        public event Action<Vm> BinaryDisplayChanged;

        public Vm(byte[] memory)
        {
            M = (byte[])memory.Clone();
            Pc = 0;
            Status = true;
            SevenSegmentDisplay = 0xff;
            LED = 0;
        }


        public void Step()
        {
            OpCode op = (OpCode)ReadByte();

            switch (op)
            {
                case OpCode.KA:
                    Status = true;
                    // Check for keys 0..9
                    for (int i = 0x30; i <= 0x39; ++i)
                    {
                        short state = GetKeyState(i);
                        if ((state & 0x8000) != 0)
                        {
                            Status = false;
                            A = (byte)(i-0x30);
                            break;
                        }
                    }
                    // Check for keys A..F
                    for (int i = 0x41; i <= 0x46; ++i)
                    {
                        short state = GetKeyState(i);
                        if ((state & 0x8000) != 0)
                        {
                            Status = false;
                            A = (byte)(10 + (i-0x41));
                            break;
                        }
                    }
                    break;

                case OpCode.AO:
                    SevenSegmentDisplay = A;
                    Action<Vm> local = SevenSegmentDisplayChanged;
                    if (local != null)
                        local(this);
                    Status = true;
                    break;

                case OpCode.CH:
                    {
                        byte t;
                        t = A; A = B; B = t;
                        t = Y; Y = Z; Z = t;
                        Status = true;
                    }
                    break;

                case OpCode.CY:
                    {
                        byte t;
                        t = A; A = Y; Y = t;
                        Status = true;
                    }
                    break;

                case OpCode.AM:
                    M[0x50 + Y] = A;
                    Status = true;
                    break;

                case OpCode.MA:
                    A = M[0x50 + Y];
                    Status = true;
                    break;

                case OpCode.Mp:
                    A = (byte)(M[0x50 + Y] + A);
                    Status = A > 0xf;
                    A = (byte)(A & 0xf);
                    break;

                case OpCode.Mm:
                    A = (byte)(M[0x50 + Y] - A);
                    Status = A > 0xf;
                    A = (byte)(A & 0xf);
                    break;

                case OpCode.TIA:
                    A = ReadByte();
                    Status = true;
                    break;

                case OpCode.AIA:
                    A = (byte)(A + ReadByte());
                    Status = A > 0xf;
                    A = (byte)(A & 0xf);
                    break;

                case OpCode.TIY:
                    Y = ReadByte();
                    Status = true;
                    break;

                case OpCode.AIY:
                    Y = (byte)(Y + ReadByte());
                    Status = Y > 0xf;
                    Y = (byte)(Y & 0xf);
                    break;

                case OpCode.CIA:
                    Status = A != ReadByte();
                    break;

                case OpCode.CIY:
                    Status = Y != ReadByte();
                    break;

                case OpCode.CAL:
                    {
                        byte value = ReadByte();
                        if (Status)
                            ExecuteSubroutine((Subroutine)value);
                        Status = true;
                    }
                    break;

                case OpCode.JUMP:
                    {
                        byte value = ReadByte();
                        value = (byte)((value << 4) | ReadByte());
                        if (Status)
                        {
                            Pc = (value < 0x60) ? value : (byte)0x0;
                        }
                        Status = true;
                    }
                    break;
            }
        }

        private void ExecuteSubroutine(Subroutine subroutine)
        {
            switch (subroutine)
            {
                case Subroutine.RSTO:
                    {
                        SevenSegmentDisplay = 0xff;
                        Action<Vm> local = SevenSegmentDisplayChanged;
                        if (local != null)
                            local(this);
                    }
                    break;

                case Subroutine.SETR:
                    {
                        LED |= (byte)((1 << Y) & 0x7f);
                        Action<Vm> local = BinaryDisplayChanged;
                        if (local != null)
                            local(this);
                    }
                    break;

                case Subroutine.RSTR:
                    {
                        LED &= (byte)~(((1 << Y) & 0x7f));
                        Action<Vm> local = BinaryDisplayChanged;
                        if (local != null)
                            local(this);
                    }
                    break;

                case Subroutine.CMPL:
                    A = (byte)((~A) & 0xf);
                    break;

                case Subroutine.CHNG:
                    {
                        byte t;
                        t = A; A = Aa; Aa = t;
                        t = B; B = Bb; Bb = t;
                        t = Y; Y = Yy; Yy = t;
                        t = Z; Z = Zz; Zz = t;
                    }
                    break;

                case Subroutine.SIFT:
                    Status = (A & 1) == 1;
                    A = (byte)(A >> 1);
                    break;

                case Subroutine.ENDS:
                    Beep(400, 200);
                    break;

                case Subroutine.ERRS:
                    Beep(261, 200);
                    break;

                case Subroutine.SHTS:
                    Beep(800, 200);
                    break;

                case Subroutine.LONS:
                    Beep(800, 500);
                    break;

                case Subroutine.SUND:
                    if (A >= 1 && A <= 0xe)
                    {
                        int[] freq = new int[]
                        {
                            220, 247, 261, 294, 330,
                            349, 392, 440, 494, 523,
                            587, 659, 698, 784,
                        };
                        Beep(freq[A-1], 200);
                    }
                    break;

                case Subroutine.TIMR:
                    Thread.Sleep((A + 1) * 100);
                    break;

                case Subroutine.DSPR:
                    {
                        int upper = M[0x5f] & 0x7;
                        int lower = M[0x5e] & 0xf;
                        LED = (byte)((upper << 4) | lower);
                        Action<Vm> local = BinaryDisplayChanged;
                        if (local != null)
                            local(this);
                    }
                    break;

//                 case Subroutine.DEMm:
//                     break;
//                 case Subroutine.DEMp:
//                     break;

                default:
                    throw new NotImplementedException(subroutine.ToString());
            }
        }

        private byte ReadByte()
        {
            byte value = M[Pc++];
            if (Pc == M.Length)
                Pc = 0;
            return value;
        }

    }
}
